package com.letoken.platform.repo.web3.provider;

import com.letoken.platform.pub.res.web3.MoralisTransaction;
import com.letoken.platform.pub.res.web3.ResultTransaction;
import com.letoken.platform.pub.res.token.TokenErc20TxRes;
import com.letoken.platform.pub.res.token.TokenTxRes;
import com.letoken.platform.pub.util.MoralisUtil;
import com.letoken.platform.repo.wallet.po.Web3ChainProvider;
import com.letoken.platform.repo.web3.util.EvmScanUtil;
import com.letoken.platform.repo.web3.util.EvmUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.web3j.abi.TypeDecoder;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.protocol.Web3j;
import org.web3j.tx.Transfer;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;


/**
 * @author letokenlabs
 * @date 2022/12/14$
 */
@Slf4j
@Component
public class EvmBaseProvider implements IEvmProvider {

    public String network;

    public Web3ChainProvider provider;

    public void init(Web3ChainProvider web3ChainProvider) {
        this.provider = web3ChainProvider;
        this.network = web3ChainProvider.getNetwork();
    }

    @Override
    public Web3ChainProvider getWeb3ChainProvider() {
        return this.provider;
    }

    @Override
    public String getGasLimit() {
        return provider.getGasLimit();
    }


    /**
     * 获取最新区块高度
     *
     * @return
     */
    @Override
    public long blockHeight() {
        long blockHeigh = EvmUtil.blockHeight(getWeb3j());
        if (blockHeigh < 1) {
            try {
                blockHeigh = EvmScanUtil.blockHeight(provider);
            } catch (Exception ex) {
            }
        }
        return blockHeigh;
    }

    /**
     * 获取GasPrice
     *
     * @return wei
     */
    @Override
    public BigInteger gasPrice() {
        BigInteger price = EvmUtil.getGasPrice(getWeb3j());
        if (price == null || price == BigInteger.ZERO) {
            try {
                price = EvmScanUtil.getGasPrice(provider);
            } catch (Exception ex) {
            }
        }
        BigInteger defaultGasPrice = new BigInteger(StringUtils.isEmpty(provider.getGasPrice()) ? "0" : provider.getGasPrice());
        if (price.compareTo(defaultGasPrice) < 0) {
            price = defaultGasPrice;
        }
        return price;
    }

    /**
     * 获取钱包余额（bnb）
     *
     * @param address 用户钱包地址
     * @return wei
     */
    @Override
    public String balance(String address) {
        String amount = EvmUtil.getBalance(address, getWeb3j());
        if (amount == null) {
            try {
                return EvmScanUtil.getBalance(address, provider);
            } catch (Exception ex) {
            }
        }
        return amount;
    }

    /**
     * 获取钱包代币余额（erc20)
     *
     * @param contractAddress 代币合约地址
     * @param address         用户钱包地址
     * @return wei
     */
    @Override
    public String tokenBalance(String contractAddress, String address) {
        String amount = EvmUtil.getTokenBalance(contractAddress, address, getWeb3j());
        if (amount == null) {
            try {
                return EvmScanUtil.getTokenBalance(contractAddress, address, provider);
            } catch (Exception ex) {
            }
        }
        return amount;
    }

    /**
     * 获取Web3j链接对象
     * 如果这个地址不知道怎么获取 可以参考  https://blog.csdn.net/sail331x/article/details/115395131
     *
     * @return
     */
    @Override
    public Web3j getWeb3j() {
        try {
            return EvmUtil.getWeb3j(provider);
        } catch (Exception ex) {
        }
        return null;
    }

    /**
     * 发送原生代币
     *
     * @param privateKey 钱包秘钥
     * @param toAddress  收款钱包地址
     * @param value      转账数量(wei)
     * @return txHash
     * @throws Exception
     */
    @Override
    public String transfer(String privateKey, String toAddress, BigDecimal value) throws Exception {

        return EvmUtil.sendEth(getWeb3j(), privateKey, toAddress, value.toBigInteger(), gasPrice(), Transfer.GAS_LIMIT);
    }

    /**
     * 发送ERC20代币
     *
     * @param contractAddress 代币合约地址
     * @param privateKey      私钥
     * @param toAddress       收款地址
     * @param value           数量(wei)
     * @return txHash
     */
    @Override
    public String transferToken(String contractAddress, String privateKey, String toAddress, BigDecimal value) {

        return EvmUtil.transferToken(getWeb3j(), contractAddress, privateKey, toAddress, value, gasPrice(), new BigInteger(getGasLimit()));
    }

    @Override
    public String getTxReceiptstatus(String txHash) {
        String status = EvmUtil.getTxReceiptstatus(txHash, getWeb3j());
        if (status == null) {
            try {
                return EvmScanUtil.getTxReceiptstatus(txHash, provider);
            } catch (Exception ex) {
            }
        }
        return status;
    }

    @Override
    public ResultTransaction getTransactionByHash(String txHash) {
        ResultTransaction rs = null;
        try {
            rs = EvmUtil.getTransactionByHash(getWeb3j(), txHash);
            if (rs == null) {
                MoralisTransaction tx = MoralisUtil.getTransactionByHash(txHash, network);
                if (tx != null) {
                    rs = new ResultTransaction();
                    rs.setHash(tx.getHash());
                    rs.setBlockNumber(tx.getBlockNumber());
                    rs.setBlockHash(tx.getBlockHash());
                    rs.setFromAddress(tx.getFromAddress());
                    rs.setToAddress(tx.getToAddress());
                    rs.setGas(tx.getGas());
                    rs.setValue(tx.getValue());
                    rs.setReceiptStatus(tx.getReceiptStatus());
                    rs.setAddress(tx.getReceiptContractAddress());

                    if (tx.getLogs() != null && !tx.getLogs().isEmpty()) {
                        rs.setValue(TypeDecoder.decodeNumeric(tx.getLogs().get(0).getString("data"), Uint256.class).getValue().toString());
                        rs.setReciever(TypeDecoder.decodeAddress(tx.getLogs().get(0).getString("topic2")).toString());
                        rs.setFromAddress(TypeDecoder.decodeAddress(tx.getLogs().get(0).getString("topic1")).toString());
                    }
                }
            }
        } catch (Exception ex) {

        }
        return rs;
    }

    @Override
    public List<TokenErc20TxRes> getTokenErc20Txs(String contractAddress, String walletAddress, Integer page, Integer pageSize, String sort) {
        try {
            return EvmScanUtil.getTokenErc20Txs(provider, contractAddress, walletAddress, page, pageSize, sort);
        } catch (Exception ex) {
            return MoralisUtil.getTokenErc20Txs(network, contractAddress, walletAddress, page, pageSize, sort);
        }
    }

    @Override
    public List<TokenTxRes> getTokenTxs(String walletAddress, Integer page, Integer pageSize, String sort) {
        try {
            return EvmScanUtil.getTokenTxs(provider, walletAddress, page, pageSize, sort);
        } catch (Exception ex) {
            return MoralisUtil.getTokenTxs(network, walletAddress, page, pageSize, sort);
        }
    }

    @Override
    public List<ResultTransaction> getBlockTokenTransactions(String tokenAddress, String blockNumber) {
        try {
            List<ResultTransaction> list = EvmUtil.getBlockTokenTransactions(getWeb3j(), tokenAddress, blockNumber);

            if (list == null) {
                // try by moralis
                List<ResultTransaction> list2 = MoralisUtil.getBlockTokenTransactions(network, blockNumber);
                if (list2 != null) {
                    list = new ArrayList<>();
                    if (!list2.isEmpty()) {
                        for (ResultTransaction tx : list2) {
                            if (StringUtils.isEmpty(tx.getReciever())) continue;
                            if (!StringUtils.isEmpty(tokenAddress)) {
                                if (!tokenAddress.equalsIgnoreCase(tx.getToAddress()))
                                    continue;
                            }
                            list.add(tx);
                        }
                    }
                }
            }
            return list;
        } catch (Exception ex) {
            return null;
        }
    }

    @Override
    public List<ResultTransaction> getBlockTokenTransactionsByMoralis(String tokenAddress, String blockNumber) {
        try {
            List<ResultTransaction> list = null;

            List<ResultTransaction> list2 = MoralisUtil.getBlockTokenTransactions(network, blockNumber);
            if (list2 != null) {
                list = new ArrayList<>();
                if (!list2.isEmpty()) {
                    for (ResultTransaction tx : list2) {
                        if (StringUtils.isEmpty(tx.getReciever())) continue;
                        if (!StringUtils.isEmpty(tokenAddress)) {
                            if (!tokenAddress.equalsIgnoreCase(tx.getToAddress()))
                                continue;
                        }
                        list.add(tx);
                    }
                }
            }
            // try web3j
            if (list == null) {
                list = EvmUtil.getBlockTokenTransactions(getWeb3j(), tokenAddress, blockNumber);
            }

            return list;
        } catch (Exception ex) {
            return null;
        }
    }
}
